﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Diagnostics;

namespace ImageProcessing
{
    public static class YV12
    {
        /// <summary>
        /// 将YV12解码为RGB
        /// </summary>
        /// <param name="pIn">YV12图像指针</param>
        /// <param name="imageOut">RGB图像,不能为空, 且尺寸与YV12相同</param>
        /// <returns>解码时间，单位：毫秒</returns>
        public static long DecodeYV12ToRGB(IntPtr pIn, Bitmap imageOut)
        {
            CheckOutput(imageOut, "DecodeYV12ToRGB");
            
            Stopwatch watch = Stopwatch.StartNew();
            unsafe
            {
                lock (imageOut)
                {
                    int W = imageOut.Width, H = imageOut.Height;
                    long size = W * H;
                    int indexY = 0, indexUV = 0;

                    byte* yData = (byte*)pIn.ToPointer();
                    byte* vData = &yData[size];
                    byte* uData = &vData[size >> 2];

                    BitmapData dataOutRGB = imageOut.LockBits(new Rectangle(0, 0, W, H), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

                    byte* pOutRGB = (byte*)dataOutRGB.Scan0.ToPointer();

                    byte y = 0, u = 0, v = 0;
                    double temp = 0;

                    for (int h = 0; h < H; h++)
                    {
                        for (int w = 0; w < W; w++)
                        {
                            indexY = h * W + w;
                            indexUV = h / 2 * W / 2 + w / 2;

                            y = yData[indexY];
                            v = vData[indexUV];
                            u = uData[indexUV];

                            indexY *= 3;

                            temp = 1.164383 * (y - 16) + 2.017230 * (u - 128) + 0;
                            pOutRGB[indexY++] = (byte)(temp < 0 ? 0 : (temp > 255 ? 255 : temp));

                            temp = (1.164383 * (y - 16) - 0.391762 * (u - 128) - 0.812969 * (v - 128));
                            pOutRGB[indexY++] = (byte)(temp < 0 ? 0 : (temp > 255 ? 255 : temp));

                            temp = 1.164383 * (y - 16) + 0 + 1.596016 * (v - 128);
                            pOutRGB[indexY] = (byte)(temp < 0 ? 0 : (temp > 255 ? 255 : temp));
                        }
                    }

                    imageOut.UnlockBits(dataOutRGB);
                }
            }

            return watch.ElapsedMilliseconds;
        }

        /// <summary>
        /// 将YV12解码为Grey
        /// </summary>
        /// <param name="pIn">YV12图像指针</param>
        /// <param name="imageOut">Grey图像,不能为空, 且尺寸与YV12相同</param>
        /// <returns>解码时间，单位：毫秒</returns>
        public static long DecodeYV12ToGrey(IntPtr pIn, Bitmap imageOut)
        {
            CheckOutput(imageOut, "DecodeYV12ToGrey");
            

            Stopwatch watch = Stopwatch.StartNew();

            unsafe
            {
                byte* yData = (byte*)pIn.ToPointer();
                lock (imageOut)
                {
                    int W = imageOut.Width, H = imageOut.Height;
                    long size = W * H;
                    int indexY = 0;

                    BitmapData dataOutGrey = imageOut.LockBits(new Rectangle(0, 0, W, H), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

                    byte* pOutGrey = (byte*)dataOutGrey.Scan0.ToPointer();

                    for (int h = 0; h < H; h++)
                    {
                        for (int w = 0; w < W; w++)
                        {
                            indexY = h * W + w;

                            pOutGrey[indexY * 3] =
                                pOutGrey[indexY * 3 + 1] =
                                pOutGrey[indexY * 3 + 2] =
                                yData[indexY];
                        }
                    }

                    imageOut.UnlockBits(dataOutGrey);
                }
            }

            return watch.ElapsedMilliseconds;
        }

        /// <summary>
        /// 将YV12流转换成灰度二维矩阵
        /// </summary>
        /// <param name="pIn"></param>
        /// <param name="bytesOut"></param>
        /// <returns></returns>
        public static long DecodeYV12ToGreyBytes(IntPtr pIn, byte[,] bytesOut)
        {
            CheckOutput(bytesOut, "DecodeYV12ToGreyBytes");

            Stopwatch watch = Stopwatch.StartNew();
            
            unsafe
            {
                byte* yData = (byte*)pIn.ToPointer();
                lock (bytesOut)
                {
                    int W = bytesOut.GetLength(0);
                    int H = bytesOut.GetLength(1);
                    for (int h = 0; h < H; h++)
                    {
                        for (int w = 0; w < W; w++)
                        {
                            bytesOut[w, h] = yData[h * W + w];
                        }
                    }
                }
            }

            return watch.ElapsedMilliseconds;
        }

        public static long GreyBytesToBitmap(byte[,] bytesIn, Bitmap imageOut)
        {
            CheckInOut(bytesIn, imageOut, "GreyBytesToBitmap");

            Stopwatch watch = Stopwatch.StartNew();

            lock (imageOut)
            {
                int W = imageOut.Width, H = imageOut.Height;
                long size = W * H;
                int indexY = 0;
                BitmapData dataOutGrey = imageOut.LockBits(new Rectangle(0, 0, W, H), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
                unsafe
                {
                    byte* pOutGrey = (byte*)dataOutGrey.Scan0.ToPointer();
                    for (int h = 0; h < H; h++)
                    {
                        for (int w = 0; w < W; w++)
                        {
                            indexY = h * W + w;

                            pOutGrey[indexY * 3] =
                                pOutGrey[indexY * 3 + 1] =
                                pOutGrey[indexY * 3 + 2] =
                                bytesIn[w, h];
                        }
                    }
                }

                imageOut.UnlockBits(dataOutGrey);
            }


            return watch.ElapsedMilliseconds;

        }

        private static void CheckInOut(object input, object output, string function)
        {
            if (input == null)
                throw new ApplicationException("input image is null|" + function);
            if (output == null)
                throw new ApplicationException("output image is null|" + function);
        }

        private static void CheckInput(object input, string function)
        {
            if (input == null)
                throw new ApplicationException("input image is null|" + function);
        }

        private static void CheckOutput(object output, string function)
        {
            if (output == null)
                throw new ApplicationException("output image is null|" + function);
        }
    }
}
